#include <winbuf/winbuf.h>

#ifndef CONFIG_WINBUF_SHM
#define CONFIG_WINBUF_SHM
#endif    

NX_PRIVATE NX_LIST_HEAD(gBaseWinbufList);
NX_U32 * gBaseWinbuf = NX_NULL;
NX_Size gBaseWinbufWidth = 0;
NX_Size gBaseWinbufHeight = 0;
NX_PRIVATE NX_Bool gBaseWinbufInit = NX_False;
NX_PRIVATE Winbuf * gRootWinbuf = NX_NULL;
NX_PRIVATE Winbuf * gTopWinbuf = NX_NULL;
NX_PRIVATE NX_Solt gWinbufMutex = NX_SOLT_INVALID_VALUE;

NX_IMPORT NX_Error WinbufHalInit(void);
NX_IMPORT void WinbufHalExit(void);
NX_IMPORT NX_Error WinbufHalPoll(void);

NX_INLINE NX_U32 GetWinbufPixel(Winbuf * wb, NX_Offset x, NX_Offset y)
{
    return wb->buf[y * wb->width + x];
}

NX_INLINE void SetWinbufPixel(Winbuf * wb, NX_Offset x, NX_Offset y, NX_U32 pixel)
{
    wb->buf[y * wb->width + x] = pixel;
}

NX_INLINE void SutBasePixel(NX_Offset x, NX_Offset y, NX_U32 pixel)
{
    gBaseWinbuf[y * gBaseWinbufWidth + x] = pixel;
}

NX_PRIVATE NX_Error __FlushWinbuf(Winbuf * wb, WinbufRegion * region)
{
    NX_Offset bufX0, bufY0, bufX1, bufY1;
    NX_Offset baseX0, baseY0, baseX1, baseY1;
    NX_Offset posX, posY;
    NX_Size bufWidth;
    NX_Size bufHeight;

    if (region == NX_NULL)
    {
        bufX0 = 0;
        bufY0 = 0;
        bufWidth = wb->width;
        bufHeight = wb->height;
    }
    else
    {
        bufX0 = region->x;
        bufY0 = region->y;
        bufWidth = region->width;
        bufHeight = region->height;
    }

    if (bufWidth > wb->width)
    {
        bufWidth = wb->width;
    }
    if (bufHeight > wb->height)
    {
        bufHeight = wb->height;
    }

    NX_SSize w = NX_MIN((NX_Offset)bufWidth, (NX_Offset)(wb->width - bufX0));
    NX_SSize h = NX_MIN((NX_Offset)bufHeight, (NX_Offset)(wb->height - bufY0));

    if (w <= 0 || h <= 0)
    {
        return NX_EINVAL;
    }
    
    baseX1 = NX_MIN((NX_Offset)(wb->x + w), (NX_Offset)gBaseWinbufWidth);
    baseY1 = NX_MIN((NX_Offset)(wb->y + h), (NX_Offset)gBaseWinbufHeight);

    posX = wb->x;
    posY = wb->y;

    if (posX < 0)
    {
        posX = 0;
    }

    if (posY < 0)
    {
        posY = 0;
    }

    for (baseY0 = posY, bufY1 = bufY0; baseY0 < baseY1; baseY0++, bufY1++)
    {
        for (baseX0 = posX, bufX1 = bufX0; baseX0 < baseX1; baseX0++, bufX1++)
        {
            NX_U32 pixel = GetWinbufPixel(wb, bufX1, bufY1);
            if (pixel >> 24)
            {
                SutBasePixel(baseX0, baseY0, pixel);
            }
        }
    }
    return NX_EOK;
}

Winbuf * CreateWinbuf(NX_Size width, NX_Size height)
{
    NX_Size bufSz;

    if (!width || !height)
    {
        return NX_NULL;
    }

    Winbuf * wb = NX_MemAlloc(sizeof(Winbuf));
    if (wb == NX_NULL)
    {
        NX_Printf("winbuf: alloc winbuf struct failed!\n");
        return NX_NULL;
    }

    bufSz = width * height * WINBUF_PIXEL_SIZE;

#ifdef CONFIG_WINBUF_SHM
    char shmName[NX_SHAREMEM_NAME_LEN] = {0};
    NX_StrPrintfN(shmName, NX_SHAREMEM_NAME_LEN, "winbuf%p+%d", wb, NX_ThreadGetCurrentId());
    wb->shm = NX_ShareMemOpen(shmName, bufSz, NX_SHAREMEM_CREATE_NEW);
    if (wb->shm < 0)
    {
        NX_Printf("winbuf: open shm %s failed err %s!\n", shmName, NX_ErrorToString(NX_ErrorGet()));
        NX_MemFree(wb);
        return NX_NULL;
    }
    NX_StrCopy(wb->shmName, shmName);
    wb->shmAddr = NX_SOLT_INVALID_VALUE;
    wb->buf = NX_ShareMemMap(wb->shm, &wb->shmAddr);
    if (!wb->buf)
    {
        NX_Printf("winbuf: map shm %s failed err %s!\n", shmName, NX_ErrorToString(NX_ErrorGet()));
        NX_ShareMemClose(wb->shm);
        NX_MemFree(wb);
        return NX_NULL;
    }
#else
    wb->buf = NX_MemAlloc(bufSz);
    if (wb->buf == NX_NULL)
    {
        NX_MemFree(wb);
        return NX_NULL;
    }
#endif

    NX_MemZero(wb->buf, bufSz);

    wb->fifo = NX_FifoCreate(sizeof(WinbufMsg) * WINBUF_FIFO_MSG_NR);
    if (wb->fifo == NX_NULL)
    {
        NX_Printf("winbuf: create fifo failed!\n");
#ifdef CONFIG_WINBUF_SHM
        NX_ShareMemUnmap(wb->shmAddr);
        NX_ShareMemClose(wb->shm);
        NX_MemFree(wb);
#else
        NX_MemFree(wb->buf);
        NX_MemFree(wb);
#endif
        return NX_NULL;
    }

    wb->width = width;
    wb->height = height;
    wb->x = 0;
    wb->y = 0;
    NX_ListInit(&wb->list);
    
    return wb;
}

NX_Error DestroyWinbuf(Winbuf * wb)
{
    if (!wb)
    {
        return NX_EINVAL;
    }

    NX_MutexAcquire(gWinbufMutex);
    if (NX_ListFind(&wb->list, &gBaseWinbufList))
    {
        NX_ListDel(&wb->list);
    }
    gTopWinbuf = NX_ListFirstEntryOrNULL(&gBaseWinbufList, Winbuf, list);
    NX_MutexRelease(gWinbufMutex);
    
    if (wb->fifo)
    {
        NX_FifoDestroy(wb->fifo);
        wb->fifo = NX_NULL;
    }

    if (wb->buf)
    {
#ifdef CONFIG_WINBUF_SHM
        NX_ShareMemUnmap(wb->shmAddr);
        NX_ShareMemClose(wb->shm);
#else
        NX_MemFree(wb->buf);
#endif
        wb->buf = NX_NULL;
    }

    NX_MemFree(wb);
    return NX_EOK;
}

NX_Error DrawRectWinbuf(Winbuf * wb, WinbufRegion * region, NX_U32 color)
{
    NX_Offset bufX0, bufY0, bufX1, bufY1;
    NX_Size bufWidth;
    NX_Size bufHeight;

    if (region == NX_NULL)
    {
        bufX0 = 0;
        bufY0 = 0;
        bufWidth = wb->width;
        bufHeight = wb->height;
    }
    else
    {
        bufX0 = region->x;
        bufY0 = region->y;
        bufWidth = region->width;
        bufHeight = region->height;
    }

    if (bufWidth > wb->width)
    {
        bufWidth = wb->width;
    }
    if (bufHeight > wb->height)
    {
        bufHeight = wb->height;
    }

    NX_SSize w = NX_MIN((NX_Offset)bufWidth, (NX_Offset)(wb->width - bufX0));
    NX_SSize h = NX_MIN((NX_Offset)bufHeight, (NX_Offset)(wb->height - bufY0));

    if (w <= 0 || h <= 0)
    {
        return NX_EINVAL;
    }
    
    if (bufX0 < 0)
    {
        bufX0 = 0;
    }

    if (bufY0 < 0)
    {
        bufY0 = 0;
    }

    for (bufY1 = bufY0; bufY1 < bufY0 + h; bufY1++)
    {
        for (bufX1 = bufX0; bufX1 < bufX0 + w; bufX1++)
        {
            if (color >> 24)
            {
                SetWinbufPixel(wb, bufX1, bufY1, color);
            }
        }
    }
    return NX_EOK;
}

NX_Error DrawWinbufBlock(Winbuf * wb, NX_U32 *buf)
{
    if (!wb)
    {
        return NX_EINVAL;
    }
    if (!wb->buf || !wb->width || !wb->height)
    {
        return NX_EPERM;
    }
    NX_MemCopy(wb->buf, buf, wb->width * wb->height * WINBUF_PIXEL_SIZE);
    return NX_EOK;
}

NX_Error SetWinbufPostion(Winbuf * wb, NX_Offset x, NX_Offset y)
{
    if (!wb)
    {
        return NX_EINVAL;
    }
    if (!wb->buf || !wb->width || !wb->height)
    {
        return NX_EINVAL;
    }

    wb->x = x;
    wb->y = y;

    return NX_EOK;
}

NX_Error TopWinbuf(Winbuf * wb)
{
    if (!wb)
    {
        return NX_EINVAL;
    }
    if (!wb->buf || !wb->width || !wb->height)
    {
        return NX_EINVAL;
    }

    if (gBaseWinbufInit == NX_False)
    {
        return NX_EPERM;
    }

    NX_MutexAcquire(gWinbufMutex);
    if (NX_ListFind(&wb->list, &gBaseWinbufList))
    {
        NX_ListDel(&wb->list);
    }
    NX_ListAddTail(&wb->list, &gBaseWinbufList);
    gTopWinbuf = wb;
    __FlushWinbuf(wb, NX_NULL); /* flush win buf */
    NX_MutexRelease(gWinbufMutex);

    return NX_EOK;
}

NX_Error FlushWinbuf(Winbuf * wb, WinbufRegion * region)
{
    NX_Error err;
    if (!wb)
    {
        return NX_EINVAL;
    }
    if (!wb->buf || !wb->width || !wb->height)
    {
        return NX_EINVAL;
    }

    if (gBaseWinbufInit == NX_False)
    {
        return NX_EPERM;
    }

    NX_MutexAcquire(gWinbufMutex);
    if (wb != gTopWinbuf)
    {
        NX_MutexRelease(gWinbufMutex);
        return NX_EPERM;
    }

    err = __FlushWinbuf(wb, region);
    NX_MutexRelease(gWinbufMutex);
    return err;
}

NX_Error PutWinbufMsg(Winbuf * wb, WinbufMsg * msg)
{
    if (!wb)
    {
        return NX_EINVAL;
    }
    if (!wb->fifo)
    {
        return NX_EPERM;
    }
    if (NX_FifoPut(wb->fifo, (const NX_U8 *)msg, sizeof(WinbufMsg)) != sizeof(WinbufMsg))
    {
        return NX_ETIMEOUT;
    }
    return NX_EOK;
}

NX_Error GetWinbufMsg(Winbuf * wb, WinbufMsg * msg)
{
    if (!wb)
    {
        return NX_EINVAL;
    }
    if (!wb->fifo)
    {
        return NX_EPERM;
    }
    NX_MemZero(msg, sizeof(WinbufMsg));
    if (NX_FifoGet(wb->fifo, (const NX_U8 *)msg, sizeof(WinbufMsg)) != sizeof(WinbufMsg))
    {
        return NX_ETIMEOUT;
    }
    return NX_EOK;
}

Winbuf * GetTopWinbuf(void)
{
    NX_MutexAcquire(gWinbufMutex);
    Winbuf * wb = gTopWinbuf;
    NX_MutexRelease(gWinbufMutex);
    return wb;
}

NX_Size GetBaseWinbufWidth(void)
{
    return gBaseWinbufWidth;
}

NX_Size GetBaseWinbufHeight(void)
{
    return gBaseWinbufHeight;
}

void SetRootWinbuf(Winbuf * wb)
{
    gRootWinbuf = wb;
}

Winbuf * GetRootWinbuf(void)
{
    return gRootWinbuf;
}

NX_Error WinbufInit(void)
{
    NX_Error err;
    if (gBaseWinbufInit)
    {
        return NX_EPERM;
    }
    
    /* init winbuf env */
    gBaseWinbuf = NX_NULL;
    gBaseWinbufWidth = 0;
    gBaseWinbufHeight = 0;
    NX_ListInit(&gBaseWinbufList);
    gRootWinbuf = NX_NULL;
    gWinbufMutex = NX_MutexCreate(0);
    if (gWinbufMutex < 0)
    {
        return NX_ENOMEM;
    }
    
    NX_Printf("WinbufHalInit\n");
    
    if ((err = WinbufHalInit()) != NX_EOK)
    {
        NX_MutexDestroy(gWinbufMutex);
        return err;
    }
    
    NX_Printf("WinbufHalInit done\n");
    
    gBaseWinbufInit = NX_True;
    NX_Printf("init winbuf ok!\n");
    return NX_EOK;
}

NX_Error WinbufLoop(void)
{
    while (1)
    {
        WinbufHalPoll();
    }
}
